package random;

import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.Arrays;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.LinkedHashSet;
import java.util.Collection;
import java.util.Collections;
import java.lang.Object;

import util.*;
import GA.ChromosomeFormer;
import GA.MethodDeclared;
import GA.ConstructorDeclared;
import Operations.ValuesGenerator;
public class TestClusterBuilder {
	private final Set<Class<?>> classToTest ;
	private final Map<String, Class<?>> cluster;
	private final Map<String, String[]>  refFactory;
	private final Map<String, ConstructorDeclared[]> clazzConstructors;
	private final Map<String, MethodDeclared[]> methodsToTest;

	public TestClusterBuilder(Set<Class<?>> cut){
		
		this.classToTest=cut;
		cluster = new LinkedHashMap<String, Class<?>>();
		refFactory= new LinkedHashMap<String, String[]>();
		methodsToTest= new LinkedHashMap<String, MethodDeclared[]>();
		clazzConstructors= new LinkedHashMap<String, ConstructorDeclared[]>();
		calculateCluster(cut);
		// Create a reference  for each clazz 
		 createRefMap(cluster);
		 
		// calculate constructors, methods, and assignableTo
		reflection(this.classToTest);



	}
	
	public  Set<Class<?>> getClassToTest(){
		return this.classToTest;		
	}

	public  Map<String, Class<?>>  getCluster(){
		return this.cluster;		
	}
	public  Map<String, ConstructorDeclared[]>  getClazzConstructors(){
		return this.clazzConstructors;		
	}

	public  Map<String, String[]>   getRefFactory(){
		return this.refFactory;		
	}
	public  Map<String, MethodDeclared[]>  getMethodsToTest(){
		return this.methodsToTest;		
	}

	private void calculateCluster(Set<Class<?>>  cutClasses)  {
		Set<Class<?>> toDo = new HashSet<Class<?>>();
		toDo.addAll(cutClasses);
		while(!toDo.isEmpty()) {
			Class<?> clazz = toDo.iterator().next();
			toDo.remove(clazz);
			String className=clazz.getName();
			if(cluster.get(className) == null) {
//			System.out.println("TestClusterBuilder.calculateCluster Class: " + className);
			//Class<?> javaClass = Class.forName(Filter.PrimitiveGenericConverter(className));
//			if (!Filter.canUse(clazz)) continue;
				cluster.put(className,clazz);
				if(Filter.isPrimitiveOrStringType(clazz)) continue;
				if (!Filter.canUse(clazz)) continue;
				// Include types used in public fields
				for(Field f : clazz.getFields()) {
					if(toDo.contains(f.getType().getName())) continue;
						toDo.add(f.getType());
				}
				for(Constructor<?> cns : clazz.getConstructors()) {
				//	System.out.printf ("\t Considering Constructor %s%n", cns);
						for(Class<?> param : cns.getParameterTypes()){
							if(param.isArray()) continue;
							toDo.add(param);
						}
				}
				
				if(!cutClasses.contains(clazz)) continue;
				for(Method meth : clazz.getDeclaredMethods()) {
	//				 System.out.printf ("\t\tcalculateCluster Considering method %s%n", meth);

						
					if(Modifier.isPrivate(meth.getModifiers())) continue;
						if(Filter.canUse(meth)){
							for(Class<?> param : meth.getParameterTypes()){
								if(param.isArray()) continue;
										toDo.add(param);
							}
						
							// add the return type to the test cluster
							final Class<?> returnType = meth.getReturnType();
							if(		!returnType.isArray() // arrays are supported via additional methods
									&& !returnType.isEnum()  // ISSUE #1: if you need support for enums, vote here: http://code.google.com/p/testful/issues/detail?id=1
									&& !returnType.equals(clazz)
									&& returnType != Void.TYPE && !returnType.isPrimitive()){ 
//								System.out.println(""+meth.getReturnType()+ "  " + meth.getGenericReturnType());
								toDo.add(meth.getReturnType());
							}

						}
				}

			}

		}
	}

	private void reflection(Set<Class<?>>  cutClasses){
		//  for(Class<?> clz : cutClasses) {
		for( Entry<String, Class<?>> c : cluster.entrySet()) {
			 Class<?> clazz = c.getValue();
			  if(Filter.isPrimitiveOrStringType(clazz)) continue;
//			 System.out.println("TestClusterBuilder.reflection  find constructor: "+clazz.getName());
			 if(cutClasses.contains(clazz)) 
						calculateMethods(clazz);		
			  calculateConstructors(clazz);
		}
		}
	private void calculateMethods(Class<?> javaClass) {
		Set<MethodDeclared> mlist =  new HashSet<MethodDeclared>();
		for(Method meth : javaClass.getDeclaredMethods()) {
				if(Modifier.isPrivate(meth.getModifiers())) continue;
				if(!Filter.canUse(meth)) continue;
//				System.out.println("\t TestClusterBuilder.calculateMethods  methodToTest: "+meth);
			
				Class<?> returnType;
				if(meth.getReturnType() ==  Void.TYPE || meth.getReturnType().isArray() || meth.getReturnType().isEnum())
					returnType = null;
				else
					returnType = meth.getReturnType();

				mlist.add(new MethodDeclared(javaClass, meth.getName(),
						Modifier.isStatic(meth.getModifiers()), meth.getParameterTypes(),
						returnType,null,null));
			}
		MethodDeclared[] methods = mlist.toArray(new MethodDeclared[mlist.size()]);

	     Arrays.sort(methods);

		methodsToTest.put(javaClass.getName(), methods);
		}
	private void calculateConstructors(Class<?> javaClass) {
		
		Set<ConstructorDeclared> clist =  new HashSet<ConstructorDeclared>();
//		List<String> refKeys = new ArrayList<String>(refFactory.keySet()); 
//		for(int refIndx=0;refIndx<refKeys.size();refIndx++){
//			String cName =(String) refKeys.get(refIndx);
//			
//			if(!cName.equals(javaClass.getName())) continue;
//			
//			String[] refrences = refFactory.get(cName);
//			Constructor<?>[] cnsList=javaClass.getConstructors();
		//	for (int i = 0; i < refrences.length && cnsList.length!=0; i++) {
			for(Constructor<?> cns : javaClass.getConstructors()) {
				if(!Filter.canUse(cns)) continue;
				Class<?>[] paramsType = cns.getParameterTypes();
				Object[] actualParams = new Object[cns.getParameterTypes().length];
				String[] params = new String[paramsType.length];
				//calculateConstants
				List<Field> constantsList= new ArrayList<Field>();
				Field[] constants = javaClass.getFields();
				for(Field field : constants) {
					if(Filter.skip(field)) continue;
						constantsList.add(field);
				}
				String ref=getReference(javaClass.getName(),refFactory);
				clist.add(new ConstructorDeclared(ref,javaClass,params, cns.getParameterTypes(),constantsList,cns));

			}
			ConstructorDeclared[] constructors = clist.toArray(new ConstructorDeclared[clist.size()]);
			if(clist.size()!=0)
				Arrays.sort(constructors);
		
			clazzConstructors.put(javaClass.getName(), constructors);

	//	}
	}

	private void createRefMap(Map<String, Class<?>> cluster) {
	//	refMap = new HashMap<Clazz, Reference[]>();
		for( Entry<String, Class<?>> c : cluster.entrySet()) {
			String[] refs = new String[GAConfig.NumVarCut];
			for(int i = 0; i < refs.length; i++)
					refs[i] = c.getKey().substring(c.getKey().lastIndexOf(".") + 1)+ "_" + i;

			refFactory.put(c.getKey(), refs);
		}
	}
	public  String getReference(String cName,Map<String, String[]>  refFactory) {		
		String refs[] = refFactory.get(cName);
		if(refs == null) return null;
		return refs[ValuesGenerator.getRandomInt(refs.length)];
	}
	   /**
     * Filters un-testable classes from the whole list
     * */
    public static Set<Class<?>> filterUntestableClasses(Set<Class<?>> classes) {
      Set<Class<?>> retClasses = new LinkedHashSet<Class<?>>();
      //filter unvisible (non-public) and abstract classes
      for(Class<?> clz : classes) {
        if(Filter.canUse(clz))
        	retClasses.add(clz);
      }      
      return retClasses;
    }

	 /**
     * Read from the provided class file 
     * */
    public static List<Class<?>>  readClassFromFile() {

    	List<Class<?>>   classToTest = new LinkedList<Class<?>>();

		if (GAConfig.classFilePath != null) {
//			System.out.println("Read tested class from file: "+ GAConfig.classFilePath);
			try {
				BufferedReader br = new BufferedReader(new FileReader(new File(
						GAConfig.classFilePath)));
				String line = br.readLine();
				while (line != null) {
					String className = line.trim();
					if (!className.equals("")&&!className.startsWith("//")) {
			               Class<?> clazz = Class.forName(className);
			               classToTest.add(clazz);
					}
					line = br.readLine();
				}
			}    catch (FileNotFoundException e) {
		          e.printStackTrace();
		          System.exit(0);
	        } catch (IOException e) {
	          e.printStackTrace();
	        } catch (ClassNotFoundException e) {
	          e.printStackTrace();
	          System.exit(0);
	        }

		}

		return classToTest;
}
    public String toString() {
    	String str="";
    	int totalMethodToTest=0;
 //   	System.out.println("  Cluster ");
		for( Entry<String, Class<?>> c : cluster.entrySet()) {
			if (true) continue;
			 Class<?> clazz = c.getValue();
			 System.out.println("\t"+clazz.getName());
	
				 ConstructorDeclared[]conList = clazzConstructors.get(clazz.getName());
				 for(int i=0;  conList!=null&&i<conList.length;i++){
					 System.out.println("\t\t"+c.getKey() + "." +conList[i].toString());
	
				 }

		}
		for( Entry<String,  MethodDeclared[]> c : methodsToTest.entrySet()) {		
			
			 MethodDeclared[]mList = c.getValue();
			 totalMethodToTest+=mList.length;
/*******************************************************************
 *  Display methods under test for the class under test
 */
//			 for(int i=0; i<mList.length ;i++)
//				 System.out.println("\t"+c.getKey() + "." +mList[i].toString());
		}
		// no want to print reference list

		System.out.println("  --------------------------------  ");
    	System.out.println(totalMethodToTest +"  Methods Under Test  ");	
		System.out.println("  --------------------------------  ");
		 if (GAConfig.SaveData) Log.log ("  --------------------------------  ");
		 if (GAConfig.SaveData) Log.log (totalMethodToTest +"  Methods Under Test  ");
		 if (GAConfig.SaveData) Log.log ("  --------------------------------  ");

//		System.out.println("  -----------------  ");
//    	System.out.println("  Reference Factory  ");	
//		System.out.println("  -----------------  ");
		for( Entry<String,  String[]> c : refFactory.entrySet() ) {			
			if (true) continue;
			String[]mList = c.getValue();
			System.out.print("\t"+c.getKey() +" = " );
			 for(int i=0; i<mList.length;i++)
				 System.out.print(mList[i]+" , ");
			 System.out.println();
		}
   	
    	
    	return str;
    }
	public  static void main(String args[]) throws ClassNotFoundException{
		ValuesGenerator.resetSeed(10);
		 Set<Class<?>> allClasses = new LinkedHashSet<Class<?>>();
	      //get class from file
	      allClasses.addAll(TestClusterBuilder.readClassFromFile());

	      System.out.println("There are " + allClasses.size() + " classes as input");
	      
	      System.out.println(allClasses);
	      

	      Set<Class<?>> classesToTest = TestClusterBuilder.filterUntestableClasses(allClasses);
	      System.out.println("There are " + classesToTest.size() + " classes to test after filtering");
	      System.out.println(classesToTest);

		// calculate the test cluster and the set of types involved in the
			TestClusterBuilder TestCluster = new TestClusterBuilder(classesToTest);

			TestCluster.toString();

//	      List<String> clusterKey = new ArrayList<String>(TestCluster.getClassToTest().keySet()); 
	      System.out.println();

	}

}
